// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2018-2023.
 * Description: irq: add affinity hint modification notify
 * Author: liuchao
 * Create: 2023-10-10
 */

#include <linux/irq.h>
#include <linux/interrupt.h>

#include <linux/proc_fs.h>
#include <linux/seq_file.h>

#include "affinity_notify.h"

struct affinity_hint_record {
	struct list_head	entry;
	unsigned int		irq;
	struct cpumask		saved_affinity_hint;
};

/*
 * Given that irq core infrastructure only keeps a reference to affinity
 * hint for each interrupt and doesn't care about its value, we have to
 * record all the values for them, or we would fail to recognise all the
 * affinity hint value modifications on an unchanged reference.
 */
static LIST_HEAD(affinity_hint_records);

/*
 * Affinity hint notify facilities provide a way to let user space processes,
 * like irqbalance, know whether affinity hint of any IRQ is modified or not
 * and decide further operations based on hint modifications.
 */
static int affinity_hint_notify;

/*
 * We only define one big spinlock to guard affinity_hint_records and notify,
 * simplifying the code at the cost of slightly performance drop in a slow
 * path. If someone wants to optimize this, just go ahead.
 */

static DEFINE_SPINLOCK(affinity_hint_lock);

/*
 * State accessors
 *
 * Note: make sure /proc/irq/affinity_hint_notify is registered before
 * calling following accessors.
 */

static void irq_modify_affinity_hint(int status)
{
	unsigned long flags;

	spin_lock_irqsave(&affinity_hint_lock, flags);
	affinity_hint_notify = status;
	spin_unlock_irqrestore(&affinity_hint_lock, flags);
}

void irq_set_affinity_hint_notify(void)
{
	irq_modify_affinity_hint(1);
}

void irq_clr_affinity_hint_notify(void)
{
	irq_modify_affinity_hint(0);
}

int irq_get_affinity_hint_notify(void)
{
	return affinity_hint_notify;
}

/*
 * File operations for /proc/irq/affinity_hint_notify
 */

static int affinity_hint_notify_show(struct seq_file *m, void *v)
{
	seq_printf(m, "%d\n", irq_get_affinity_hint_notify());
	return 0;
}

static int affinity_hint_notify_open(struct inode *inode, struct file *file)
{
	return single_open(file, affinity_hint_notify_show, PDE_DATA(inode));
}

static ssize_t affinity_hint_notify_write(struct file *file,
			const char __user *buffer,
			size_t count,
			loff_t *ppos)
{
	irq_clr_affinity_hint_notify();
	return count;
}

static const struct proc_ops affinity_hint_notify_proc_ops = {
	.proc_open	= affinity_hint_notify_open,
	.proc_read	= seq_read,
	.proc_lseek	= seq_lseek,
	.proc_release	= single_release,
	.proc_write	= affinity_hint_notify_write,
};

static int __init register_affinity_hint_notify(void)
{
	if (!proc_create("irq/affinity_hint_notify", 0600, NULL,
			 &affinity_hint_notify_proc_ops)) {
		pr_err("Failed in registering /proc/irq/affinity_hint_notify\n");
		return -ENOMEM;
	}

	pr_info("IRQ affinity hint notify enabled\n");
	irq_clr_affinity_hint_notify();
	return 0;
}
core_initcall(register_affinity_hint_notify);

void irq_verify_affinity_hint(unsigned int irq, const struct cpumask *m)
{
	struct affinity_hint_record *record, *iter, *found;
	bool destroy_record;

	record = found = NULL;
	destroy_record = !m || cpumask_empty(m);

	if (destroy_record)
		goto skip_alloc;

	record = kzalloc(sizeof(struct affinity_hint_record), GFP_KERNEL);
	if (!record) {
		pr_err("%s: memory exhausted\n", __func__);
		return;
	}

	record->irq = irq;
	cpumask_copy(&record->saved_affinity_hint, m);
	INIT_LIST_HEAD(&record->entry);

skip_alloc:
	spin_lock(&affinity_hint_lock);

	list_for_each_entry(iter, &affinity_hint_records, entry) {
		if (iter->irq == irq) {
			found = iter;
			break;
		}
	}

	if (!found && record)
		list_add_tail(&record->entry, &affinity_hint_records);

	if (destroy_record && found)
		list_del(&found->entry);

	spin_unlock(&affinity_hint_lock);

	if (destroy_record) {
		if (found) {
			kfree(found);
			goto out;
		}
		return;
	}

	if (!found)
		goto out;

	if (cpumask_equal(&found->saved_affinity_hint, m)) {
		kfree(record);
		return;
	}

	cpumask_copy(&found->saved_affinity_hint, m);
	kfree(record);
out:
	irq_set_affinity_hint_notify();
}

